#include "netlib/base/log_file.h"

#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>

#include <cerrno>
#include <functional>
#include <memory>
#include <utility>

#include "netlib/base/log_stream.h"

using std::string;
using namespace std::placeholders;

netlib::FileHelper::FileHelper(const char* filename) {
	file_ = fopen(filename, "a");
	if (!file_) {
		printf("%d\n", errno);
	}
	assert(file_);
}

netlib::FileHelper::~FileHelper() { fclose(file_); }

void netlib::FileHelper::Flush() { fflush(file_); }

void netlib::FileHelper::Append(const char* data, size_t len) {
	size_t remain = len;
	while (remain > 0) {
		size_t n = Write(data, remain);
		assert(n > 0);
		remain -= n;
		data += n;
	}
	written_size_ += len;
}

const time_t netlib::LogFile::kRollTime;

netlib::LogFile::LogFile(const std::string& basename,
                         std::string dirname,
                         int check_lines,
                         time_t interval,
                         off_t max_bytes,
                         bool is_thread_safe) :
    kMaxBytes(max_bytes),
    kFlushInterval(interval), kCheckLines(check_lines), basename_(basename),
    dirname_(std::move(dirname)), mutex_(is_thread_safe ? std::make_unique<MutexLock>() : nullptr) {
	CheckDirectory();
	assert(basename.find('/') == string::npos);
	RollFile();
	LogStream::SetOutputFunc([this](auto&& PH1, auto&& PH2) {
		Append(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2));
	});
	LogStream::SetFlushFunc([this] { Flush(); });
}

void netlib::LogFile::RollFile() {
	string filename = GetFileName();
	file_ = std::make_unique<FileHelper>(filename.c_str());
	count_ = 0;
	last_flush_ = start_time_;
}

void netlib::LogFile::Append(const char* data, size_t len) {
	if (mutex_) {
		MutexLockGuard lock(*mutex_);
		AppendWithOutLock(data, len);
	} else {
		AppendWithOutLock(data, len);
	}
}

void netlib::LogFile::Flush() {
	if (mutex_) {
		MutexLockGuard lock(*mutex_);
		file_->Flush();
	} else {
		file_->Flush();
	}
}

void netlib::LogFile::CheckDirectory() {
	if (dirname_.back() != '/') {
		dirname_ += '/';
	}
	DIR* dir = opendir(dirname_.c_str());
	if (dir) {
		closedir(dir);
	} else {
		int res = mkdir(dirname_.c_str(), 0777);
		assert(res == 0);
	}
}

void netlib::LogFile::CheckAndRoll() {
	if (file_->WrittenSize() >= static_cast<size_t>(kMaxBytes)) {
		RollFile();
	} else if (count_ >= kCheckLines) {
		count_ = 0;
		time_t now = time(nullptr);
		if (now - start_time_ >= kRollTime) {
			RollFile();
		} else if (now - last_flush_ >= kFlushInterval) {
			last_flush_ = now;
			file_->Flush();
		}
	}
}

std::string netlib::LogFile::GetFileName() {
	string filename(dirname_ + basename_);
	filename += string("_") + std::to_string(getpid());
	char timebuf[32]{0};
	struct tm tm;
	time_t now = time(nullptr);
	start_time_ = now;
	localtime_r(&now, &tm);
	strftime(timebuf, sizeof(timebuf), "_%Y%m%d_%H%M%S.log", &tm);
	return filename + timebuf;
}

void netlib::LogFile::AppendWithOutLock(const char* data, size_t len) {
	file_->Append(data, len);
	++count_;
	CheckAndRoll();
}
